package copyengine.network.roomStructure.executeLayer.impl.socket
{
	import com.adobe.serialization.json.JSON2;
	
	import copyengine.network.roomStructure.executeLayer.CENetworkRoomModelConfig;
	import copyengine.network.roomStructure.executeLayer.IExecuteHandle;
	import copyengine.utils.GeneralUtils;
	import copyengine.utils.debug.CELog;
	
	import flash.events.Event;
	import flash.events.IOErrorEvent;
	import flash.events.ProgressEvent;
	import flash.events.SecurityErrorEvent;
	import flash.net.Socket;
	import flash.utils.ByteArray;

	public final class SocketExecuteWarp
	{
		//指定当前Socket协议的版本
		private static const SOCKET_VERSION:uint = 1;

		private var executeSocket:Socket;

		private var executeHandle:IExecuteHandle;

		private var isExecuteEnable:Boolean;

		public function SocketExecuteWarp()
		{
		}

		public function initialize(_executeHandle:IExecuteHandle):void
		{
			executeHandle = _executeHandle;

			receiveDataByteArray = new ByteArray();
			receiveForwardData = new ByteArray();
			executeSocket = new Socket();
			//设置Socket的TimeOut时间为10s
			executeSocket.timeout = 10000;

			isExecuteEnable = false;

			addListener();
		}

		public function dispose():void
		{
			removeListener();
			executeSocket.close();
			executeSocket = null;
		}

		public function isEnable():Boolean
		{
			return isExecuteEnable;
		}

		public function connect(_connectURL:String, _port:int):void
		{
			CELog.log("Start Connect Socket , URL : " + _connectURL + "  port : " + _port);
			executeSocket.connect(_connectURL, _port);
		}

		/**
		 * !!注意 自己调用Close方法时候 不发送Event
		 *The close event is dispatched only when the server closes the connection;
		 * it is not dispatched when you call the close() method.
		 */
		public function disconnect():void
		{
			//当接到服务器端的数据含有ErrorCode时候,上层逻辑首先会调用disconnect断开连接
			//然后服务器端同时也会关闭连接,但是此时isExecuteEnable已经是false了
			//所以接收到服务器端发送过来的Event 不认为是异常流程
			isExecuteEnable = false;
			executeSocket.close();
			executeHandle.onExecuteDisconnected(CENetworkRoomModelConfig.EXECUTE_WARP_TYPE_SOCKET);
		}

		//====================================================
		//==SendData
		//====================================================
		/**
		 * @param _command 		发送给服务器端的命令
		 * @param _commandArg	命令带的参数(JSON格式)
		 * @param _forwardData		转发的数据包-加密后的ByteArray(服务器端不进行处理)
		 */
		public function sendData(_command:uint, _commandArg:Object, _forwardData:ByteArray = null):void
		{
			//4+2+4+4
			//Command是一个uint,是四字节
			//两个Boolean 个是一个字节(isHaveJsonArg和isHaveFowardData)
			//Json的长度和ByteArray的长度个四字节
			var totalPackageLenght:int = 18;

			//===JSON
			var isHaveJsonArg:Boolean = false;
			var jsonCommandArgLenght:uint = 0;
			var jsonCommandArg:String;
			if (_commandArg != null)
			{
				jsonCommandArg = JSON2.encode(_commandArg);
				isHaveJsonArg = true;
				jsonCommandArgLenght = jsonCommandArg.length;
			}

			//===ForwardData
			var isHaveFowardData:Boolean = false;
			var forwardDataLenght:uint = 0;
			if (_forwardData != null)
			{
				isHaveFowardData = true;
				forwardDataLenght = _forwardData.length;
			}

			totalPackageLenght += jsonCommandArgLenght;
			totalPackageLenght += forwardDataLenght;

			//============
			//==Header
			//============
			//写入字节包的长度
			executeSocket.writeUnsignedInt(totalPackageLenght);

			//写入当前数据包的版本
			executeSocket.writeUnsignedInt(SOCKET_VERSION);


			//写入发送的命令
			executeSocket.writeUnsignedInt(_command);
			//写入是否有JSONCommandArg
			executeSocket.writeBoolean(isHaveJsonArg);
			//写入JSONCommandArg的长度(如果没有也会写入0)
			executeSocket.writeUnsignedInt(jsonCommandArgLenght);
			//写入是否有ForwardData
			executeSocket.writeBoolean(isHaveFowardData);
			//写入ForwardData的长度(如果没有也会写入0)
			executeSocket.writeUnsignedInt(forwardDataLenght);

			//===========
			//==Body
			//===========
			//写入数据包（JSON在前ByteArray在后）
			_commandArg && executeSocket.writeUTFBytes(jsonCommandArg);
			_forwardData && executeSocket.writeBytes(_forwardData);

			executeSocket.flush();

			CELog.log("SocketExecuteWarp-> sendData command : " + _command + " arg : " + jsonCommandArg);
		}

		//====================================================
		//==Receive Data
		//====================================================

		//接收到的数据
		private var receiveDataByteArray:ByteArray;
		//接收到的Command
		private var receiveCommand:uint;
		//接收到的CommandArg-从JSON中New出来每次均为一个新的
		private var receiveCommandArg:Object;
		//接收到的ForwardData-循环利用ByteArray(因为得到的是加密数据，上层解密后直接反转出来一个Object且会在同帧执行)
		private var receiveForwardData:ByteArray;

		//当前读取的Offset(数据有可能会分包到达)
		private var currentOffset:uint;
		//当前剩余的字节长度
		private var currentRemainLength:uint = 0;
		//标记当前是否已经开始读取数据流
		private var isStartReadPack:Boolean = false;
		//当前数据包的总长度
		private var bodyLength:int;
		//数据包是否含有JSON数据
		private var isHavaJSONData:Boolean;
		//数据包JSON数据长度
		private var jsonDataLength:uint;
		//数据包是否含有BytepArray数据
		private var isHaveByteArrayData:Boolean;
		//数据包ByteArray数据长度
		private var byteArrayDataLength:uint;

		private function onSocketReceiveDataHandler(e:ProgressEvent):void
		{
			if (!isStartReadPack)
			{
				//清除上一次读取的遗留数据
				receiveDataByteArray.clear();
				receiveForwardData.clear();
				currentOffset = 0;

				//接受数据
				isStartReadPack = true;
				//读取包儿长度
				bodyLength = executeSocket.readUnsignedInt();

				//读取Command
				receiveCommand = executeSocket.readUnsignedInt();
				//读取Json数据头
				isHavaJSONData = executeSocket.readBoolean();
				jsonDataLength = executeSocket.readUnsignedInt();

				//读取ByteArrag数据头
				isHaveByteArrayData = executeSocket.readBoolean();
				byteArrayDataLength = executeSocket.readUnsignedInt();

				//减去已读取的长度(14= 4[Command->uint]+5[isHavaJSONData->Boolean(1) +jsonDataLength->uint(4)]+5(ByteArray同JSON))
				bodyLength -= 14;

				currentRemainLength = bodyLength;
			}

			//判断当前数据包是否粘包
			var isNeedReadAgain:Boolean;

			if (currentRemainLength > 0)
			{
				var currentAvailableLenght:int = executeSocket.bytesAvailable;

				//只读取需要部分的
				var readyPackageSize:int = Math.min(currentRemainLength, currentAvailableLenght);
				//如果更具包头定义的数据包大小 比当前Socket中的数据包要少 则当前Socket还有剩余字节需要读取（发生粘包）
				readyPackageSize < currentAvailableLenght ? isNeedReadAgain = true : isNeedReadAgain = false;

				//只读取需要部分
				executeSocket.readBytes(receiveDataByteArray, currentOffset, readyPackageSize);
				currentOffset += readyPackageSize;
				currentRemainLength -= readyPackageSize;
			}

			if (currentRemainLength <= 0)
			{
				if (isHavaJSONData)
				{
					var receiveCommandArgStr:String = receiveDataByteArray.readUTFBytes(jsonDataLength);
					receiveCommandArg = JSON2.decode(receiveCommandArgStr);
				}
				if (isHaveByteArrayData)
				{
					receiveDataByteArray.position = jsonDataLength;
					receiveDataByteArray.readBytes(receiveForwardData);
				}

				CELog.log("SocketExecuteWarp -> onSocketReceiveDataHandler receive command : " + receiveCommand + " arg : " + receiveCommandArgStr);
				//SocketHandle中需要释放receiveCommand,receiveCommandArg,receiveForwardData的引用
				//因为在函数下一帧有可能会接收到新的数据 此时会重复利用这些对象
				executeHandle.onReceiveExecuteData(CENetworkRoomModelConfig.EXECUTE_WARP_TYPE_SOCKET,
												   receiveCommand,
												   receiveCommandArg,
												   receiveForwardData);

				//当前包儿已经解完
				isStartReadPack = false;
			}

			//出现粘包且剩余部分超过一个包头,需要重新解析一次当前Socke包
			//首先要判断是否出现粘包 因为当不粘包情况下 无法读取executeSocket.bytesAvailable 会抛异常
			if (isNeedReadAgain && executeSocket.bytesAvailable > 14)
			{
				onSocketReceiveDataHandler(null);
			}

		}

		//====================================================
		//==Event Handle
		//====================================================

		private function addListener():void
		{
			GeneralUtils.addTargetEventListener(executeSocket, Event.CLOSE, onSocketCloseHandler);
			GeneralUtils.addTargetEventListener(executeSocket, Event.CONNECT, onSocketConnectHandler);
			GeneralUtils.addTargetEventListener(executeSocket, IOErrorEvent.IO_ERROR, onSockectErrorHandler);
			GeneralUtils.addTargetEventListener(executeSocket, SecurityErrorEvent.SECURITY_ERROR, onSocketSecurityErrorHandler);
			GeneralUtils.addTargetEventListener(executeSocket, ProgressEvent.SOCKET_DATA, onSocketReceiveDataHandler);
		}

		private function removeListener():void
		{
			GeneralUtils.removeTargetEventListener(executeSocket, Event.CLOSE, onSocketCloseHandler);
			GeneralUtils.removeTargetEventListener(executeSocket, Event.CONNECT, onSocketConnectHandler);
			GeneralUtils.removeTargetEventListener(executeSocket, IOErrorEvent.IO_ERROR, onSockectErrorHandler);
			GeneralUtils.removeTargetEventListener(executeSocket, SecurityErrorEvent.SECURITY_ERROR, onSocketSecurityErrorHandler);
			GeneralUtils.removeTargetEventListener(executeSocket, ProgressEvent.SOCKET_DATA, onSocketReceiveDataHandler);
		}

		private function onSocketCloseHandler(e:Event):void
		{
			if (isExecuteEnable)
			{
				CELog.err("SocketExecuteWarp->onSocketCloseHandler socket disconnect but isExecuteEnable == true");
				executeHandle.onExecuteError(CENetworkRoomModelConfig.EXECUTE_WARP_TYPE_SOCKET, e);
				isExecuteEnable = false;
			}
		}

		private function onSocketConnectHandler(e:Event):void
		{
			isExecuteEnable = true;
			executeHandle.onExecuteConnected(CENetworkRoomModelConfig.EXECUTE_WARP_TYPE_SOCKET);
		}

		private function onSockectErrorHandler(e:IOErrorEvent):void
		{
			CELog.err("SocketExecuteWarp->onSocketIOErrorHandler");
			executeHandle.onExecuteError(CENetworkRoomModelConfig.EXECUTE_WARP_TYPE_SOCKET, e);
			disconnect();
		}

		private function onSocketSecurityErrorHandler(e:SecurityErrorEvent):void
		{
			CELog.err("SocketExecuteWarp->onSocketSecurityErrorHandler");
			executeHandle.onExecuteError(CENetworkRoomModelConfig.EXECUTE_WARP_TYPE_SOCKET, e);
			disconnect();
		}

	}
}
